/*
	The Raja Toolkit.
	Copyright (C) 2011 Raja-Toolkit Foundation

	This library is free software; you can redistribute it and/or
	modify it under the terms of the GNU Lesser General Public
	License as published by the Free Software Foundation; either
	version 3.0 of the License, or (at your option) any later version.

	This library is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
	Lesser General Public License for more details.

	You should have received a copy of the GNU Lesser General Public
	License along with this library; if not, write to the Free Software
	Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*/

#include "transforms.hpp"

namespace raja
{
namespace math
{

/// @name orthogonal 2d projection matrix (both tested)
/// @{
template<typename T>
tmat4<T> mat4_ortho(T left, T right, T bottom, T top)
{
	return mat4_ortho<T>(left, right, bottom, top, T(-1), T(1));
}

template<typename T>
tmat4<T> mat4_ortho(const tvec2<T>& lb, const tvec2<T>& rt)
{
	return mat4_ortho(tvec3<T>(lb.x, lb.y, T(-1)), tvec3<T>(rt.x, rt.y, T(1)));
}
/// @}

/// orthogonal 3d projection matrix (both tested)
/// @{
template<typename T>
tmat4<T> mat4_ortho(T left, T right, T bottom, T top, T near, T far)
{
	T	width	= right - left;
	T	height	= top - bottom;
	T	depth	= far - near;
	T	r00	= T(2) / width;
	T	r11	= T(2) / height;
	T	r22	= T(-2) / depth;
	T	r03	= -(right + left) / width;
	T	r13	= -(top + bottom) / height;
	T	r23	= -(far + near) / depth;
	return tmat4<T>(r00, T(0), T(0), T(0),
					T(0), r11, T(0), T(0),
					T(0), T(0), r22, T(0),
					r03, r13, r23, T(1));
}

template<typename T>
tmat4<T> mat4_ortho(const tvec3<T> &lbn, const tvec3<T> &rtf)
{
	return mat4_ortho(lbn.x, rtf.x, lbn.y, rtf.y, lbn.z, rtf.z);
}
/// @}

///////////////////////////////////////////////////////////////////////////////
template tmat4<real32> mat4_ortho<real32>(real32 left, real32 right, real32 bottom, real32 top, real32 near, real32 far);
template tmat4<real64> mat4_ortho<real64>(real64 left, real64 right, real64 bottom, real64 top, real64 near, real64 far);

template tmat4<real32> mat4_ortho<real32>(const tvec3<real32> &lbn, const tvec3<real32> &rtf);
template tmat4<real64> mat4_ortho<real64>(const tvec3<real64> &lbn, const tvec3<real64> &rtf);

template tmat4<real32> mat4_ortho<real32>(const tvec2<real32>& lb, const tvec2<real32>& rt);
template tmat4<real64> mat4_ortho<real64>(const tvec2<real64>& lb, const tvec2<real64>& rt);

template tmat4<real32> mat4_ortho<real32>(real32 left, real32 right, real32 bottom, real32 top);
template tmat4<real64> mat4_ortho<real64>(real64 left, real64 right, real64 bottom, real64 top);
///////////////////////////////////////////////////////////////////////////////

/// translation matrix (tested)
template<typename T>
tmat4<T> mat4_translation(const tvec3<T>& trans)
{
	return tmat4<T>(T(1), T(0), T(0), T(0),
					T(0), T(1), T(0), T(0),
					T(0), T(0), T(1), T(0),
					T(trans.x), T(trans.y), T(trans.z), T(1));
}

///////////////////////////////////////////////////////////////////////////////
template tmat4<real32> mat4_translation<real32>(const tvec3<real32>& trans);
template tmat4<real64> mat4_translation<real64>(const tvec3<real64>& trans);
///////////////////////////////////////////////////////////////////////////////

/// scale matrix (tested)
template<typename T>
tmat4<T> mat4_scale(const tvec3<T>& scale)
{
	return tmat4<T>(scale.x, T(0), T(0), T(0),
					T(0), scale.y, T(0), T(0),
					T(0), T(0), scale.z, T(0),
					T(0), T(0), T(0), T(1));
}

///////////////////////////////////////////////////////////////////////////////
template tmat4<real32> mat4_scale<real32>(const tvec3<real32>& scale);
template tmat4<real64> mat4_scale<real64>(const tvec3<real64>& scale);
///////////////////////////////////////////////////////////////////////////////

/// @name rotation matrix
/// @{
template<typename T>
tmat4<T> mat4_rotation(const tquat<T> &q)
{
	return mat4_from(q);
}

///////////////////////////////////////////////////////////////////////////////
template tmat4<real32> mat4_rotation<real32>(const tquat<real32> &q);
template tmat4<real64> mat4_rotation<real64>(const tquat<real64> &q);
///////////////////////////////////////////////////////////////////////////////

template<typename T>
tmat4<T> mat4_rotation(T angle, const tvec3<T>& axis)
{
	tquat<T> q(axis, angle);
	return mat4_from(q);
}

///////////////////////////////////////////////////////////////////////////////
template tmat4<real32> mat4_rotation(real32 angle, const tvec3<real32>& axis);
template tmat4<real64> mat4_rotation(real64 angle, const tvec3<real64>& axis);
///////////////////////////////////////////////////////////////////////////////

/// @}

/// project a point to the screen (tested)
template<typename T>
tvec3<T> project(const tmat4<T>& world, const tmat4<T>& persp, const tvec2<T>& lb, const tvec2<T>& rt, const tvec3<T>& pt)
{
	tvec4<T>	in(pt.x, pt.y, pt.z, T(1));
	tvec4<T>	out	= persp * world * in;

	tvec3<T>	out_v;
	out_v.x	= lb.x + ((rt.x - lb.x) * half(out.x + T(1)));
	out_v.y	= lb.y + ((rt.y - lb.y) * half(out.y + T(1)));
	out_v.z	= half(out.z + T(1));

	return out_v;
}

///////////////////////////////////////////////////////////////////////////////
template tvec3<real32> project<real32>(const tmat4<real32>& world, const tmat4<real32>& persp,
									   const tvec2<real32>& lb, const tvec2<real32>& rt, const tvec3<real32>& pt);
template tvec3<real64> project<real64>(const tmat4<real64>& world, const tmat4<real64>& persp,
									   const tvec2<real64>& lb, const tvec2<real64>& rt, const tvec3<real64>& pt);
///////////////////////////////////////////////////////////////////////////////

/// unproject a point to the 3d system (tested)
template<typename T>
tvec3<T> unproject(const tmat4<T>& world, const tmat4<T>& persp, const tvec2<T>& lb, const tvec2<T>& rt, const tvec3<T>& pt)
{
	tvec4<T>	in;
	in.x	= (2.0f * (pt.x - lb.x) / (rt.x - lb.x)) - 1.0f;
	in.y	= (2.0f * (pt.y - lb.y) / (rt.y - lb.y)) - 1.0f;
	in.z	= (2.0f * pt.z) - 1.0f;
	in.w	= 1.0f;
	tvec4<T>	out = inverse(persp * world) * in;
	return tvec3<T>(out.x, out.y, out.z) / out.w;
}

///////////////////////////////////////////////////////////////////////////////
template tvec3<real32> unproject<real32>(const tmat4<real32>& world, const tmat4<real32>& persp,
										 const tvec2<real32>& lb, const tvec2<real32>& rt, const tvec3<real32>& pt);
template tvec3<real64> unproject<real64>(const tmat4<real64>& world, const tmat4<real64>& persp,
										 const tvec2<real64>& lb, const tvec2<real64>& rt, const tvec3<real64>& pt);
///////////////////////////////////////////////////////////////////////////////

///
/// frustum matrix (tested)
/// @param lbn left/bottom/near
/// @param rtf right/top/far
/// @return the frustum matrix
///
template<typename T>
tmat4<T> mat4_frustum(const tvec3<T>& lbn, const tvec3<T>& rtf)
{
	T	width	= rtf.x - lbn.x;
	T	height	= rtf.y - lbn.y;
	T	depth	= rtf.z - lbn.z;
	T	a	= (rtf.x + lbn.x) / width;
	T	b	= (rtf.y + lbn.y) / height;
	T	c	= -(rtf.z + lbn.z) / depth;
	T	d	= -(T(2) * rtf.z * lbn.z) / depth;

	return tmat4<T>(T(2) * lbn.z / width, T(0), T(0), T(0),
				T(0), T(2) * lbn.z / height, T(0), T(0),
				a, b, c, T(-1),
				T(0), T(0), d, T(0));
}

///////////////////////////////////////////////////////////////////////////////
template tmat4<real32> mat4_frustum<real32>(const tvec3<real32>& lbn, const tvec3<real32>& rtf);
template tmat4<real64> mat4_frustum<real64>(const tvec3<real64>& lbn, const tvec3<real64>& rtf);
///////////////////////////////////////////////////////////////////////////////

///
/// perspective projection matrix (tested)
/// @param fovy field of view in radians and in y direction
/// @param aspect the aspect ratio
/// @param near near plane value
/// @param far far plane value
///
template<typename T>
tmat4<T> mat4_perspective(T fovy, T aspect, T near, T far)
{
	T	f	= T(1) / tan(half(fovy));
	T	denom	= near - far;
	T	a	= (far + near) / denom;
	T	b	= (T(2) * far * near) / denom;

	return tmat4<T>(f / aspect, T(0), T(0), T(0),
				T(0), f, T(0), T(0),
				T(0), T(0), a, T(-1),
				T(0), T(0), b, T(0));
}

///////////////////////////////////////////////////////////////////////////////
template tmat4<real32> mat4_perspective<real32>(real32 fovy, real32 aspect, real32 near, real32 far);
template tmat4<real64> mat4_perspective<real64>(real64 fovy, real64 aspect, real64 near, real64 far);
///////////////////////////////////////////////////////////////////////////////

///
/// lookat matrix (tested)
/// @param eye the eye position
/// @param dest the eye destination position
/// @param up the up vector
/// @return the lookat matrix
///
template<typename T>
tmat4<T> mat4_lookat(const tvec3<T>& eye, const tvec3<T>& dest, const tvec3<T>& up)
{
	tvec3<T>	f	= normalize(dest - eye);
	tvec3<T>	s	= normalize(cross(f, up));
	tvec3<T>	u	= normalize(cross(s, f));

	return tmat4<T>(s.x, u.x, -f.x, T(0),
				s.y, u.y, -f.y, T(0),
				s.z, u.z, -f.z, T(0),
				T(0), T(0), T(0), T(1)) * mat4_translation(-eye);
}

///////////////////////////////////////////////////////////////////////////////
template tmat4<real32> mat4_lookat<real32>(const tvec3<real32>& eye, const tvec3<real32>& dest, const tvec3<real32>& up);
template tmat4<real64> mat4_lookat<real64>(const tvec3<real64>& eye, const tvec3<real64>& dest, const tvec3<real64>& up);
///////////////////////////////////////////////////////////////////////////////

///
/// @brief convert from world coordinates to local coordinates
/// @param m world matrix
/// @param in the input world coordinates
/// @return local coordinates
///
template<typename T>
tvec3<T> world3_to_local3(const tmat4<T>& world, const tvec3<T>& in)
{
	tmat4<T>	inv_world	= inverse(world);
	tvec4<T>	vin(in.x, in.y, in.z, T(1));
	tvec4<T>	vout	= inv_world * vin;
	return tvec3<T>(vout.x, vout.y, vout.z);
}

///////////////////////////////////////////////////////////////////////////////
template vec3 world3_to_local3<real32>(const mat4& world, const vec3& in);
template dvec3 world3_to_local3<real64>(const dmat4& world, const dvec3& in);
///////////////////////////////////////////////////////////////////////////////

///
/// @brief transform a vec3 by a mat4
/// @param m matrix
/// @param in input vector
/// @return the transformed vector
///
template<typename T>
tvec3<T> transform(const tmat4<T>& m, const tvec3<T>& in)
{
	tvec4<T>	vin(in.x, in.y, in.z, T(1));
	tvec4<T>	vout	= m * vin;
	return tvec3<T>(vout.x, vout.y, vout.z);
}

///////////////////////////////////////////////////////////////////////////////
template vec3 transform<real32>(const mat4& m, const vec3& in);
template dvec3 transform<real64>(const dmat4& m, const dvec3& in);
///////////////////////////////////////////////////////////////////////////////

///
/// @brief transform a vec4 by a mat4
/// @param m matrix
/// @param in input vector
/// @return the transformed vector
///
template<typename T>
tvec4<T> transform(const tmat4<T>& m, const tvec4<T>& in)
{
	return (m * in);
}

///////////////////////////////////////////////////////////////////////////////
template vec4 transform<real32>(const mat4& m, const vec4& in);
template dvec4 transform<real64>(const dmat4& m, const dvec4& in);
///////////////////////////////////////////////////////////////////////////////

}	// namespace math
}	// namespace raja
